<?php

/**
 * @package		JYCMS
 * @subpackage	代码高亮插件
 * @version		$Id: syntaxHighLighter.php 29 2013-05-07 06:45:56Z hopingsoft@gmail.com $
 * @author		Hoping
 * @copyright	Copyright (C) 2009 Hoping Software Studio. All rights reserved.
 */
// 保证此文件不被直接执行
defined ( 'IN_JYCMS' ) or die ( 'Direct Access to this location is not allowed.' );
class pluginSyntaxHighLighter extends cmsPlugins {
	var $shortcode_tags = array ();
	var $plugin_ver = '3.0.83';
	var $sh_themes = array (
			'Default',
			'Django',
			'Eclipse',
			'Emacs',
			'FadeToGrey',
			'MDUltra',
			'Midnight',
			'RDark'
	);
	var $theme = 'Default';
	var $default_atts = array (
			'num' => 1,
			'lang' => 'plain',
			'lang_name' => 'false',
			'highlight_lines' => '',
			'collapse' => 'false',
			'gutter' => 'true',
			'ruler' => 'false',
			'toolbar' => 'false',
			'smart_tabs' => 'true',
			'tab_size' => '4',
			'light' => 'false',
			'auto_link' => 'false',
			'font_size' => '10%'
	);
	var $target = array (
			'AS3',
			'Bash',
			'ColdFusion',
			'CSharp',
			'Cpp',
			'JavaScript',
			'JavaFX',
			'JAVA',
			'Delphi',
			'Diff',
			'Erlang',
			'Groovy',
			'Patch',
			'Pascal',
			'Perl',
			'PHP',
			'Python',
			'Plain',
			'PowerShell',
			'Ruby',
			'Scala',
			'Shell',
			'Text',
			'vbnet',
			'VB',
			'SQL',
			'CSS',
			'XHTML',
			'XML',
			'XSLT',
			'HTML',
			'C'
	);
	var $options;
	var $languages;
	var $parsed = false;
	function __construct($params = array()) {
		$this->pluginname = basename ( __FILE__ );
		$this->plugin_url = str_replace ( '\\', '/', cmsFS::formatpath ( dirname ( __FILE__ ) ) ) . '/';
		$this->languages = array (
				"as3" => array (
						false,
						'AS3',
						'js/shBrushAS3.js',
						'shBrushAS3'
				),
				"bash" => array (
						false,
						'Bash',
						'js/shBrushBash.js',
						'shBrushBash'
				),
				"c" => array (
						false,
						'C',
						'js/shBrushCpp.js',
						'shBrushCpp'
				),
				"cpp" => array (
						false,
						'C++',
						'js/shBrushCpp.js',
						'shBrushCpp'
				),
				"c-sharp" => array (
						false,
						'C#',
						'js/shBrushCSharp.js',
						'shBrushCSharp'
				),
				"coldfusion" => array (
						false,
						'ColdFusion',
						'js/shBrushColdFusion.js',
						'shBrushColdFusion'
				),
				"javascript" => array (
						false,
						'Java Script',
						'js/shBrushJScript.js',
						'shBrushJScript'
				),
				"java" => array (
						false,
						'JAVA',
						'js/shBrushJava.js',
						'shBrushJava'
				),
				"javafx" => array (
						false,
						'JavaFX',
						'js/shBrushJavaFX.js',
						'shBrushJavaFX'
				),
				"delphi" => array (
						false,
						'Delphi',
						'js/shBrushDelphi.js',
						'shBrushDelphi'
				),
				"diff" => array (
						false,
						'Diff',
						'js/shBrushDiff.js',
						'shBrushDiff'
				),
				"erlang" => array (
						false,
						'Erlang',
						'js/shBrushErlang.js',
						'shBrushErlang'
				),
				"groovy" => array (
						false,
						'Groovy',
						'js/shBrushGroovy.js',
						'shBrushGroovy'
				),
				"patch" => array (
						false,
						'Patch',
						'js/shBrushDiff.js',
						'shBrushDiff'
				),
				"pascal" => array (
						false,
						'Pascal',
						'js/shBrushDelphi.js',
						'shBrushDelphi'
				),
				"perl" => array (
						false,
						'Perl',
						'js/shBrushPerl.js',
						'shBrushPerl'
				),
				"php" => array (
						false,
						'PHP',
						'js/shBrushPhp.js',
						'shBrushPhp'
				),
				"plain" => array (
						false,
						'Plain Text',
						'js/shBrushPlain.js',
						'shBrushPlain'
				),
				"powershell" => array (
						false,
						'PowerShell',
						'js/shBrushPowerShell.js',
						'shBrushPowerShell'
				),
				"python" => array (
						false,
						'Python',
						'js/shBrushPython.js',
						'shBrushPython'
				),
				"ruby" => array (
						false,
						'Ruby',
						'js/shBrushRuby.js',
						'shBrushRuby'
				),
				"scala" => array (
						false,
						'Scala',
						'js/shBrushScala.js',
						'shBrushScala'
				),
				"shell" => array (
						false,
						'Shell',
						'js/shBrushBash.js',
						'shBrushBash'
				),
				"text" => array (
						false,
						'Plain Text',
						'js/shBrushPlain.js',
						'shBrushPlain'
				),
				"vb" => array (
						false,
						'VB',
						'js/shBrushVb.js',
						'shBrushVb'
				),
				"vb.net" => array (
						false,
						'VB.Net',
						'js/shBrushVb.js',
						'shBrushVb'
				),
				"sql" => array (
						false,
						'SQL',
						'js/shBrushSql.js',
						'shBrushSql'
				),
				"css" => array (
						false,
						'CSS',
						'js/shBrushCss.js',
						'shBrushCss'
				),
				"xml" => array (
						false,
						'XML',
						'js/shBrushXml.js',
						'shBrushXml'
				),
				"html" => array (
						false,
						'HTML',
						'js/shBrushXml.js',
						'shBrushXml'
				),
				"xhtml" => array (
						false,
						'XHTML',
						'js/shBrushXml.js',
						'shBrushXml'
				),
				"xslt" => array (
						false,
						'XSLT',
						'js/shBrushXml.js',
						'shBrushXml'
				)
		);
		$this->pluginSyntaxHighLighter ( $params );
	}
	function pluginSyntaxHighLighter($params = array()) {
		$this->cmsPlugins ( $params );
		$lang = unserialize ( $params ['sh_languages'] );
		if (! $lang)
			$lang = array (
					'PHP'
			);
		foreach ( $lang as $k ) {
			$k = trim ( strtolower ( $k ) );
			$this->languages [$k] [0] = true;
		}
		$this->theme = trim ( $params ['sh_themes'] );
	}

	// **************************************************************************************
	// parse shortcodes
	// **************************************************************************************
	function onParseContent($content) {
		$this->add_shortcodes ();
		$pattern = $this->get_shortcode_regex ();
		$content = preg_replace_callback ( "/$pattern/s", 'self::do_shortcode_tag', $content );
		// 如果没有检测到代码的话，就直接跳过了
		if ($this->parsed) {
			$this->add_stylesheet ();
			$this->add_footer ();
		}
		return $content;
	}

	// **************************************************************************************
	// Add shortcodes
	// **************************************************************************************
	function add_shortcodes() {
		$this->shortcode_tags ['code'] = array (
				&$this,
				'shortcode_code'
		);

		foreach ( $this->target as $val ) {
			$func = array (
					&$this,
					'shortcode_' . strtolower ( $val )
			);
			$this->shortcode_tags [$val] = $func;
			if (strtolower ( $val ) !== $val) {
				$val = strtolower ( $val );
				$this->shortcode_tags [$val] = $func;
			}
			if (strtoupper ( $val ) !== $val) {
				$val = strtoupper ( $val );
				$this->shortcode_tags [$val] = $func;
			}
		}
	}

	/**
	 * Retrieve the shortcode regular expression for searching.
	 *
	 * The regular expression combines the shortcode tags in the regular
	 * expression
	 * in a regex class.
	 *
	 * The regular expression contains 6 different sub matches to help with
	 * parsing.
	 *
	 * 1 - An extra [ to allow for escaping shortcodes with double [[]]
	 * 2 - The shortcode name
	 * 3 - The shortcode argument list
	 * 4 - The self closing /
	 * 5 - The content of a shortcode when it wraps some content.
	 * 6 - An extra ] to allow for escaping shortcodes with double [[]]
	 *
	 * @since 2.5
	 * @uses $shortcode_tags
	 *
	 * @return string The shortcode search regular expression
	 */
	function get_shortcode_regex() {
		$tagnames = array_keys ( $this->shortcode_tags );
		$tagregexp = join ( '|', array_map ( 'preg_quote', $tagnames ) );

		// WARNING! Do not change this regex without changing do_shortcode_tag()
		// and strip_shortcode_tag()
		return '\\[' . 		// Opening bracket
		'(\\[?)' . 		// 1: Optional second opening bracket for escaping shortcodes:
		           // [[tag]]
		"($tagregexp)" . 		// 2: Shortcode name
		'\\b' . 		// Word boundary
		'(' . 		// 3: Unroll the loop: Inside the opening shortcode tag
		'[^\\]\\/]*' . 		// Not a closing bracket or forward slash
		'(?:' . '\\/(?!\\])' . 		// A forward slash not followed by a closing bracket
		'[^\\]\\/]*' . 		// Not a closing bracket or forward slash
		')*?' . ')' . '(?:' . '(\\/)' . 		// 4: Self closing tag ...
		'\\]' . 		// ... and closing bracket
		'|' . '\\]' . 		// Closing bracket
		'(?:' . '(' . 		// 5: Unroll the loop: Optionally, anything between the opening and
		      // closing shortcode tags
		'[^\\[]*+' . 		// Not an opening bracket
		'(?:' . '\\[(?!\\/\\2\\])' . 		// An opening bracket not followed by the closing
		                     // shortcode tag
		'[^\\[]*+' . 		// Not an opening bracket
		')*+' . ')' . '\\[\\/\\2\\]' . 		// Closing shortcode tag
		')?' . ')' . '(\\]?)'; // 6: Optional second closing brocket for escaping shortcodes:
		            // [[tag]]
	}

	/**
	 * Regular Expression callable for do_shortcode() for calling shortcode
	 * hook.
	 *
	 * @see get_shortcode_regex for details of the match array contents.
	 *
	 * @since 2.5
	 * @access private
	 * @uses $shortcode_tags
	 *
	 * @param array $m
	 *        	Regular expression match array
	 * @return mixed False on failure.
	 */
	function do_shortcode_tag($m) {
		$this->parsed = true;
		// allow [[foo]] syntax for escaping a tag
		if ($m [1] == '[' && $m [6] == ']') {
			return substr ( $m [0], 1, - 1 );
		}

		$tag = $m [2];
		$attr = $this->shortcode_parse_atts ( $m [3] );

		if (isset ( $m [5] )) {
			// enclosing tag - extra parameter
			return $m [1] . call_user_func ( $this->shortcode_tags [$tag], $attr, $m [5], $tag ) . $m [6];
		} else {
			// self-closing tag
			return $m [1] . call_user_func ( $this->shortcode_tags [$tag], $attr, NULL, $tag ) . $m [6];
		}
	}

	/**
	 * Retrieve all attributes from the shortcodes tag.
	 *
	 * The attributes list has the attribute name as the key and the value of
	 * the
	 * attribute as the value in the key/value pair. This allows for easier
	 * retrieval of the attributes, since all attributes have to be known.
	 *
	 * @since 2.5
	 *
	 * @param string $text
	 * @return array List of attributes and their value.
	 */
	function shortcode_parse_atts($text) {
		$atts = array ();
		$pattern = '/(\w+)\s*=\s*"([^"]*)"(?:\s|$)|(\w+)\s*=\s*\'([^\']*)\'(?:\s|$)|(\w+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|(\S+)(?:\s|$)/';
		$text = preg_replace ( "/[\x{00a0}\x{200b}]+/u", " ", $text );
		if (preg_match_all ( $pattern, $text, $match, PREG_SET_ORDER )) {
			foreach ( $match as $m ) {
				if (! empty ( $m [1] ))
					$atts [strtolower ( $m [1] )] = stripcslashes ( $m [2] );
				elseif (! empty ( $m [3] ))
					$atts [strtolower ( $m [3] )] = stripcslashes ( $m [4] );
				elseif (! empty ( $m [5] ))
					$atts [strtolower ( $m [5] )] = stripcslashes ( $m [6] );
				elseif (isset ( $m [7] ) and strlen ( $m [7] ))
					$atts [] = stripcslashes ( $m [7] );
				elseif (isset ( $m [8] ))
					$atts [] = stripcslashes ( $m [8] );
			}
		} else {
			$atts = ltrim ( $text );
		}
		return $atts;
	}

	// **************************************************************************************
	// Add Shortcode
	// **************************************************************************************
	function shortcode_handler($atts, $content = null, $startTag) {
		extract ( $this->shortcode_atts ( $this->default_atts, $atts ) );

		$lang_name = (strtolower ( $lang_name ) == 'true');

		if (strtolower ( $startTag ) === 'code')
			$startTag = strtolower ( $lang );
			// 起始行号
		$pVal = ( int ) $num;
		$outTxt = '';
		$inTxt = $content;
		// echo code($inTxt);
		$inTxt = str_replace ( '<br />', "\n", $inTxt );
		$inTxt = strip_tags ( $inTxt );
		// exit(code($inTxt));
		if (isset ( $this->languages [$startTag] )) {
			$this->languages [$startTag] [0] = true;
		}

		if ($lang_name) {
			$outTxt = "\n\n" . '<p class="lang-name">' . $this->languages [$startTag] [1] . '</p>' . "\n";
		}

		$pre_tag = '<pre>';
		if (! $this->isKtai ()) {
			// -- for SyntaxHighlighter 2.x or 3.x
			$pre_tag = '<pre' . ' class="' . "brush: {$startTag};" . ($pVal > 1 ? " first-line: {$pVal};" : '') . (! empty ( $highlight_lines ) ? " highlight: [{$highlight_lines}];" : '') . (strtolower ( $collapse ) == 'true' ? ' collapse: true;' : '') . (strtolower ( $gutter ) == 'false' ? ' gutter: false;' : '') . (strtolower ( $ruler ) == 'true' ? ' ruler: true;' : '') . (strtolower ( $toolbar ) == 'false' ? ' toolbar: false;' : '') . (strtolower ( $smart_tabs ) == 'false' ? ' smart-tabs: false;' : '') . (strtolower ( $tab_size ) != '4' ? ' tab-size: ' . ( int ) $tab_size . ';' : '') . (strtolower ( $auto_link ) == 'false' ? ' auto-links: false;' : '') . (strtolower ( $light ) == 'true' ? ' light: true;' : '') . ($font_size != '100%' ? " font-size: {$font_size};" : '') . '"' . '>';
		}
		$outTxt .= "{$pre_tag}{$inTxt}</pre>\n\n";

		return $outTxt;
	}
	function shortcode_code($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'code' );
	}
	function shortcode_c($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'c' );
	}
	function shortcode_cpp($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'cpp' );
	}
	function shortcode_csharp($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'c-sharp' );
	}
	function shortcode_java($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'java' );
	}
	function shortcode_javascript($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'jscript' );
	}
	function shortcode_delphi($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'delphi' );
	}
	function shortcode_pascal($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'pascal' );
	}
	function shortcode_perl($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'perl' );
	}
	function shortcode_php($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'php' );
	}
	function shortcode_python($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'python' );
	}
	function shortcode_ruby($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'ruby' );
	}
	function shortcode_vb($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'vb' );
	}
	function shortcode_vbnet($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'vb.net' );
	}
	function shortcode_scala($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'scala' );
	}
	function shortcode_sql($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'sql' );
	}
	function shortcode_css($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'css' );
	}
	function shortcode_xml($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'xml' );
	}
	function shortcode_html($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'html' );
	}
	function shortcode_xhtml($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'xhtml' );
	}
	function shortcode_xslt($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'xslt' );
	}
	function shortcode_bash($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'bash' );
	}
	function shortcode_diff($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'diff' );
	}
	function shortcode_groovy($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'groovy' );
	}
	function shortcode_patch($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'patch' );
	}
	function shortcode_plain($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'plain' );
	}
	function shortcode_shell($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'shell' );
	}
	function shortcode_text($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'plain' );
	}
	function shortcode_as3($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'as3' );
	}
	function shortcode_coldfusion($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'coldfusion' );
	}
	function shortcode_javafx($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'javafx' );
	}
	function shortcode_erlang($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'erlang' );
	}
	function shortcode_powershell($atts, $content = null) {
		return $this->shortcode_handler ( $atts, $content, 'powershell' );
	}

	/**
	 * Combine user attributes with known attributes and fill in defaults when
	 * needed.
	 *
	 * The pairs should be considered to be all of the attributes which are
	 * supported by the caller and given as a list. The returned attributes will
	 * only contain the attributes in the $pairs list.
	 *
	 * If the $atts list has unsupported attributes, then they will be ignored
	 * and
	 * removed from the final returned list.
	 *
	 * @since 2.5
	 *
	 * @param array $pairs
	 *        	Entire list of supported attributes and their defaults.
	 * @param array $atts
	 *        	User defined attributes in shortcode tag.
	 * @return array Combined and filtered attribute list.
	 */
	function shortcode_atts($pairs, $atts) {
		$atts = ( array ) $atts;
		$out = array ();
		foreach ( $pairs as $name => $default ) {
			if (array_key_exists ( $name, $atts ))
				$out [$name] = $atts [$name];
			else
				$out [$name] = $default;
		}
		return $out;
	}

	// **************************************************************************************
	// Add stylesheet
	// **************************************************************************************
	function add_stylesheet() {
		global $control;
		if (! $this->isKtai ()) {
			$control->addCustom ( 'style', 'shCore', $this->plugin_url . "css/shCore.css", array (), '', 'all' );
			$control->addCustom ( 'style', 'shCore' . $this->theme, $this->plugin_url . 'css/shCore' . $this->theme . '.css', array (
					'shCore'
			), '', 'all' );
			$control->addCustom ( 'style', 'shTheme' . $this->theme, $this->plugin_url . 'css/shTheme' . $this->theme . '.css', array (
					'shCore'
			), '', 'all' );
		}
	}

	// **************************************************************************************
	// Add JavaScript
	// **************************************************************************************
	function add_footer() {
		global $control, $_G;
		$control->addCustom ( 'script', 'shCore', $this->plugin_url . 'js/shCore.js' );
		$enabled = false;
		foreach ( $this->languages as $key => $val ) {
			if ($val [0]) {
				$control->addCustom ( 'script', $key, $this->plugin_url . $val [2] );
				$enabled = true;
			}
		}
		if (! $enabled) {
			unset ( $_G ['custom'] ['script'] ['shCore'] );
			return;
		}
		// -- for SyntaxHighlighter 3.x
		$_G ['custom'] ['footer'] .= "SyntaxHighlighter.all();\n";
	}
}